package com.mm.base.config.web;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.mm.base.config.jwt.AuthenticationInterceptor;
import com.mm.base.constant.BasicConstants;
import com.mm.base.exception.GlobalExceptionHandler;
import com.mm.util.common.FileUploadUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.HttpMethod;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.context.request.async.TimeoutCallableProcessingInterceptor;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.resource.ResourceUrlEncodingFilter;
import org.springframework.web.servlet.resource.VersionResourceResolver;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;

import java.math.BigInteger;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @Description WebMvc配置类
 * @Date 2023/9/27 上午10:28
 * @Author yanglin
 **/
@Import({GlobalExceptionHandler.class})
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    @Qualifier("thymeleafViewResolver")
    private ThymeleafViewResolver thymeleafViewResolver;

    /**
     * 路径匹配规则
     *
     * @param configurer
     */
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        // 设置是否自动后缀模式匹配，默认真。如/user是否匹配/user/。如果真，也就是说, "/user"和"/user/"都会匹配到"/user"的Controller。
        configurer.setUseTrailingSlashMatch(true);
    }

    /**
     * 异步调用支持
     *
     * @param configurer
     */
    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        // 注册callable拦截器
        configurer.registerCallableInterceptors(timeoutInterceptor());
        // 注册deferredResult拦截器
        configurer.registerDeferredResultInterceptors();
        // 异步请求超时时间
        configurer.setDefaultTimeout(600000);
        // 设定异步请求线程池callable等, spring默认线程不可重用
        configurer.setTaskExecutor(executor());
    }

    /**
     * 最常使用，推荐。其实质是对java.util.concurrent.ThreadPoolExecutor的包装
     * <p>
     * 为@Async实现一个自定义线程池
     *
     * @return
     */
    @Bean(name = "asyncPoolTaskExecutor")
    public ThreadPoolTaskExecutor executor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //核心线程数
        taskExecutor.setCorePoolSize(10);
        //线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        taskExecutor.setMaxPoolSize(100);
        //缓存队列
        taskExecutor.setQueueCapacity(50);
        //许的空闲时间,当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
        taskExecutor.setKeepAliveSeconds(200);
        //异步方法内部线程名称
        taskExecutor.setThreadNamePrefix("async-webmvc-");
        /**
         * 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize，
         * 如果还有任务到来就会采取任务拒绝策略
         * 通常有以下四种策略：
         * ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
         * ThreadPoolExecutor.DiscardPolicy：也是丢弃任务，但是不抛出异常。
         * ThreadPoolExecutor.DiscardOldestPolicy：丢弃队列最前面的任务，然后重新尝试执行任务（重复此过程）
         * ThreadPoolExecutor.CallerRunsPolicy：重试添加当前的任务，自动重复调用 execute() 方法，直到成功
         */
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.initialize();
        return taskExecutor;
    }

    @Bean
    public TimeoutCallableProcessingInterceptor timeoutInterceptor() {
        return new TimeoutCallableProcessingInterceptor();
    }

    @Override
    public void addFormatters(FormatterRegistry registry) {
        // 布尔格式化器
        registry.addFormatter(booleanFormatter());
        // 字符串转日期转化器
        registry.addConverter(stringToDateConverter());
    }

    @Bean
    public BooleanFormatter booleanFormatter() {
        return new BooleanFormatter();
    }

    @Bean
    public StringToDateConverter stringToDateConverter() {
        return new StringToDateConverter();
    }

    /**
     * 信息转化器
     *
     * @param converters
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();

        // Long转String，因为js中得数字类型不能包含所有的java long值
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);

        // Date转String
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

        jackson2HttpMessageConverter.setObjectMapper(objectMapper);
        // 将自定义转化器放到默认转换器前面解决转换器失效
        converters.add(0, jackson2HttpMessageConverter);
    }


    /**
     * 视图控制器
     *
     * @param registry
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("login");
        registry.addViewController("/login.html").setViewName("login");

    }

    /**
     * JSPs, Thymeleaf, FreeMarker and Velocity模板引擎，可直接使用此方法增加静态文件md5
     */
    @Bean
    public FilterRegistrationBean getFilterRegistrationBean() {
        FilterRegistrationBean<ResourceUrlEncodingFilter> bean = new FilterRegistrationBean<>(new ResourceUrlEncodingFilter());
        bean.addUrlPatterns("*.html");
        return bean;
    }

    /**
     * thymeleaf设置全局变量版本号实现控制css和js缓存
     *
     * @param registry
     */
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        if (thymeleafViewResolver != null) {
            Map<String, Object> map = new HashMap<>();
            map.put("version", System.currentTimeMillis());
            thymeleafViewResolver.setStaticVariables(map);
        }
    }

    /**
     * 静态资源处理器
     * classpath:配置的是服务器的静态资源
     * file:配置的是与服务器本地磁盘上的静态资源
     *
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        String filePath = "file:" + FileUploadUtil.getProjectPath();
        registry.addResourceHandler("/upload/**").addResourceLocations(filePath);
        // js静态资源处理,添加版本号 ？？？？
        VersionResourceResolver resolver = new VersionResourceResolver();
        resolver.addContentVersionStrategy("**/**/*.js", "**/**/*.css");
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/")
                // 生产环境推荐设置为true，开发环境推荐设置为false
                // 设置true会为ResourceHttpRequestHandler添加默认的 CachingResourceResolver
                .resourceChain(false)
                .addResolver(resolver);
        ;
        registry.addResourceHandler("/common/**").addResourceLocations("classpath:/static/common/")
                // 生产环境推荐设置为true，开发环境推荐设置为false
                // 设置true会为ResourceHttpRequestHandler添加默认的 CachingResourceResolver
                .resourceChain(false)
                .addResolver(resolver);
        ;
        ;
        registry.addResourceHandler("/layui/**").addResourceLocations("classpath:/static/layui/");
        registry.addResourceHandler("/mumu/**").addResourceLocations("classpath:/static/mumu/")
                // 生产环境推荐设置为true，开发环境推荐设置为false
                // 设置true会为ResourceHttpRequestHandler添加默认的 CachingResourceResolver
                .resourceChain(false)
                .addResolver(resolver);
        registry.addResourceHandler("/swagger-ui/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/");
    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        // 添加参数解析器
        argumentResolvers.add(currentUserMethodArgumentResolver());
    }

    @Bean
    public CurrentUserMethodArgumentResolver currentUserMethodArgumentResolver() {
        return new CurrentUserMethodArgumentResolver();
    }

    /**
     * 拦截器
     *
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/note/**")
                .excludePathPatterns("/task/**")
                .excludePathPatterns("/upload/**")
                .excludePathPatterns("/swagger**/**")
                .excludePathPatterns("/webjars/**")
                .excludePathPatterns("/v3/**")
                .excludePathPatterns("/doc.html")
                .excludePathPatterns("/koTime")
                .excludePathPatterns("/koTime/**")
                .excludePathPatterns("/actuator/**");
    }

    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }

    /**
     * 此种设置跨域的方式，在自定义拦截器的情况下可能导致跨域失效
     * 原因：当跨越请求在跨域请求拦截器之前的拦截器处理时就异常返回了，
     * 那么响应的response报文头部关于跨域允许的信息就没有被正确设置，
     * 导致浏览器认为服务不允许跨域，而造成错误。
     * 解决：自定义跨域过滤器解决跨域问题（该过滤器最好放在其他过滤器之前）
     * 跨域失效-使用addCorsMappings对跨域相关配置，然后再使用了自定义拦截器，可能会导致跨域配置失效。
     * 使用过滤器设置跨域允许信息，因为过滤器先于拦截器执行
     * 允许跨域请求
     */
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration(BasicConstants.STAR_STAR, corsConfig());
        return new CorsFilter(source);
    }

    /**
     * Jackson全局转化long类型为String，解决jackson序列化时long类型缺失精度问题
     *
     * @return
     */
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        Jackson2ObjectMapperBuilderCustomizer customizer = jacksonObjectMapperBuilder -> {
            jacksonObjectMapperBuilder.serializerByType(BigInteger.class, ToStringSerializer.instance);
            jacksonObjectMapperBuilder.serializerByType(Long.class, ToStringSerializer.instance);
            //空值 null 进行序列化
            jacksonObjectMapperBuilder.featuresToEnable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
            jacksonObjectMapperBuilder.serializationInclusion(JsonInclude.Include.ALWAYS);
            // 指定日期格式
            // 序列化
            jacksonObjectMapperBuilder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            // 反序列化
            jacksonObjectMapperBuilder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        };
        return customizer;
    }

    /**
     * 跨域配置
     *
     * @return
     */
    private CorsConfiguration corsConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        // 请求常用的三种配置，*代表允许所有，当时你也可以自定义属性（比如header只能带什么，只能是post方式等等）
        corsConfiguration.addAllowedOriginPattern(BasicConstants.STAR);
        corsConfiguration.addAllowedHeader(BasicConstants.STAR);
        corsConfiguration.addAllowedMethod(HttpMethod.OPTIONS);
        corsConfiguration.addAllowedMethod(HttpMethod.GET);
        corsConfiguration.addAllowedMethod(HttpMethod.POST);
        corsConfiguration.addAllowedMethod(HttpMethod.PATCH);
        corsConfiguration.addAllowedMethod(HttpMethod.PUT);
        corsConfiguration.addAllowedMethod(HttpMethod.DELETE);
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.setMaxAge(3600L);
        return corsConfiguration;
    }

    @Bean
    public ByteArrayHttpMessageConverter byteArrayHttpMessageConverter() {
        return new ByteArrayHttpMessageConverter();
    }

    /**
     * 自定义异常处理器
     *
     * @param resolvers
     */
    /*@Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
         resolvers.add(webMvcExceptionResolver());
    }

    @Bean
    public WebMvcExceptionResolver webMvcExceptionResolver() {
        return new WebMvcExceptionResolver();
    }*/
}
